﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Metadata;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

using Windows.Win32;
using System.ComponentModel;

namespace BorderlessFormExtended;

partial class _WinFormDesignerDisabler { }

 
public abstract partial class BorderlessFormCore : Form
{

    private bool _borderless = true;
    private bool _resizable = true;
    private bool _dragable = true;
    private bool _borderlessShadow = true;


        
    const WINDOW_STYLE WINDOWED_STYLE = WINDOW_STYLE.WS_OVERLAPPEDWINDOW;

    const WINDOW_STYLE DWM_BORDERLESS_STYLE = WINDOW_STYLE.WS_POPUP | WINDOW_STYLE.WS_THICKFRAME | WINDOW_STYLE.WS_CAPTION | WINDOW_STYLE.WS_SYSMENU | WINDOW_STYLE.WS_MAXIMIZEBOX | WINDOW_STYLE.WS_MINIMIZEBOX;

    const WINDOW_STYLE BASIC_BORDERLESS_STYLE = WINDOW_STYLE.WS_POPUP | WINDOW_STYLE.WS_THICKFRAME | WINDOW_STYLE.WS_SYSMENU | WINDOW_STYLE.WS_MAXIMIZEBOX | WINDOW_STYLE.WS_MINIMIZEBOX;


    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    internal new FormBorderStyle FormBorderStyle
    {
        get => base.FormBorderStyle;
        set => base.FormBorderStyle = value;
    }

    protected override CreateParams CreateParams
    {
        get
        {
            var cp = base.CreateParams;

            cp.Style = (int)GetCurrentWindowStyle();

            cp.ExStyle |= (int)WINDOW_EX_STYLE.WS_EX_NOREDIRECTIONBITMAP;

            return cp;
        }
    }

    private WINDOW_STYLE GetCurrentWindowStyle()
    {
        var style = _borderless ? SelectBorderStyle() : WINDOWED_STYLE;

        if (!MaximizeBox)
        {
            style &= ~WINDOW_STYLE.WS_MAXIMIZEBOX;
        }

        if (!MinimizeBox)
        {
            style &= ~WINDOW_STYLE.WS_MINIMIZEBOX;
        }

        return style;
    }

    public BorderlessFormCore()
    {
        AutoScaleMode = AutoScaleMode.Dpi;
    }

    private bool IsMaximized(HWND hwnd)
    {
        WINDOWPLACEMENT placement = new();
        if (!GetWindowPlacement(hwnd, ref placement))
        {
            return false;
        }
        return placement.showCmd == SHOW_WINDOW_CMD.SW_MAXIMIZE;
    }

    private bool IsCompositonEnabled()
    {
        var success = DwmIsCompositionEnabled(out var pfEnabled);
        return success.Succeeded && pfEnabled;
    }

    private WINDOW_STYLE SelectBorderStyle()
    {
        return IsCompositonEnabled() ? DWM_BORDERLESS_STYLE : BASIC_BORDERLESS_STYLE;
    }



    private void SetBorderless(bool enable)
    {

        _borderless = enable;

        var newStyle = GetCurrentWindowStyle();

        var oldStyle = (WINDOW_STYLE)GetWindowLong(_hwnd, WINDOW_LONG_PTR_INDEX.GWL_STYLE);

        if (newStyle == oldStyle) return;

        SetWindowLong(_hwnd, WINDOW_LONG_PTR_INDEX.GWL_STYLE, (int)newStyle);

        SetShadow(_hwnd, _borderlessShadow && _borderless);

        SetWindowPos(_hwnd, HWND.Null, 0, 0, 0, 0, SET_WINDOW_POS_FLAGS.SWP_FRAMECHANGED | SET_WINDOW_POS_FLAGS.SWP_NOMOVE | SET_WINDOW_POS_FLAGS.SWP_NOSIZE);
        ShowWindow(_hwnd, SHOW_WINDOW_CMD.SW_SHOW);

    }

    private void SetBorderlessShadow(bool enable)
    {
        if (_borderless)
        {
            _borderlessShadow = enable;
            SetShadow(_hwnd, enable);
        }
    }

    private void SetShadow(HWND hwnd, bool enable)
    {
        MARGINS[] shadow = new MARGINS[] {
            new MARGINS(){ cxLeftWidth = 0, cxRightWidth = 0, cyTopHeight = 0, cyBottomHeight = 0 },
            new MARGINS(){ cxLeftWidth = 0, cxRightWidth = 0, cyTopHeight = 1, cyBottomHeight = 0 }
        };

        if (IsCompositonEnabled())
        {
            DwmExtendFrameIntoClientArea(hwnd, shadow[enable ? 1 : 0]);
        }
    }

    HWND _hwnd;
    protected override void CreateHandle()
    {
        base.CreateHandle();

        _hwnd = new(Handle);

        SetBorderless(_borderless);
        SetBorderlessShadow(_borderlessShadow);


        unsafe
        {
            var type = DWM_SYSTEMBACKDROP_TYPE.DWMSBT_MAINWINDOW;

            DwmSetWindowAttribute(_hwnd, DWMWINDOWATTRIBUTE.DWMWA_SYSTEMBACKDROP_TYPE, &type, (uint)sizeof(DWM_SYSTEMBACKDROP_TYPE));
        }
        


    }

    private void AdjustMaximizedClientRect(HWND hwnd, ref RECT rect)
    {
        if (!IsMaximized(hwnd)) return;
        var hMonitor = MonitorFromWindow(hwnd, MONITOR_FROM_FLAGS.MONITOR_DEFAULTTONULL);

        if (hMonitor.IsNull) return;
        MONITORINFO monitorInfo = new();

        if (!GetMonitorInfo(hMonitor, ref monitorInfo)) return;

        rect = monitorInfo.rcWork;
    }

    protected override void WndProc(ref Message m)
    {

        var msg = (uint)m.Msg;
        var wParam = m.WParam;
        var lParam = m.LParam;
        var hwnd = new HWND(m.HWnd);

        switch (msg)
        {
            case WM_NCCALCSIZE:
                {
                    if (wParam == 1 && _borderless)
                    {
                        var param = Marshal.PtrToStructure<NCCALCSIZE_PARAMS>(lParam);

                        AdjustMaximizedClientRect(hwnd, ref param.rgrc._0);

                        m.Result = 0;

                        return;
                    }

                }
                break;
            case WM_NCACTIVATE:
                {
                    if (!IsCompositonEnabled())
                    {
                        m.Result = 1;
                        return;
                    }
                }
                break;
            case WM_NCHITTEST:
                {
                    if (_borderless)
                    {
                        m.Result = (nint)HitTestNCA(lParam);

                        return;
                    }
                }
                break;
            case WM_KEYDOWN:
            case WM_SYSKEYDOWN:
                {
                    
                    var key = (VIRTUAL_KEY)wParam.ToInt32();
                    
                    switch (key)
                    {
                        case VIRTUAL_KEY.VK_F1: { SetBorderless(!_borderless); m.Result = 0; } return;
                        case VIRTUAL_KEY.VK_F2: { SetBorderlessShadow(!_borderlessShadow); m.Result = 0; } return;
                        case VIRTUAL_KEY.VK_F3: { _dragable = !_dragable; m.Result = 0; } return;
                        case VIRTUAL_KEY.VK_F4: { _resizable = !_resizable; m.Result = 0; } return;
                    }
                }

                break;

        }

        base.WndProc(ref m);
    }

    private enum HitTestNCARegionMask : byte
    {
        client = 0b0000,
        left = 0b0001,
        right = 0b0010,
        top = 0b0100,
        bottom = 0b1000,
    }

    private uint HitTestNCA(nint lParam)
    {
        var cursor = PARAM.ToPoint(lParam);

        var border = new Point(GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CXFRAME) + GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CXPADDEDBORDER), GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CYFRAME) + GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CXPADDEDBORDER));


        if (!GetWindowRect(_hwnd, out var windowRect))
        {
            return HTNOWHERE;
        }

        var drag = _dragable ? HTCAPTION : HTCLIENT;

        var result =
            (byte)HitTestNCARegionMask.left * (cursor.X < (windowRect.left + border.X) ? 1 : 0) |
            (byte)HitTestNCARegionMask.right * (cursor.X >= (windowRect.right - border.X) ? 1 : 0) |
            (byte)HitTestNCARegionMask.top * (cursor.Y < (windowRect.top + border.Y) ? 1 : 0) |
            (byte)HitTestNCARegionMask.bottom * (cursor.Y >= (windowRect.bottom - border.Y) ? 1 : 0);

        switch (result)
        {
            case (byte)HitTestNCARegionMask.left:
                return _resizable ? HTLEFT : drag;
            case (byte)HitTestNCARegionMask.right:
                return _resizable ? HTRIGHT : drag;
            case (byte)HitTestNCARegionMask.top:
                return _resizable ? HTTOP : drag;
            case (byte)HitTestNCARegionMask.bottom:
                return _resizable ? HTBOTTOM : drag;
            case (byte)(HitTestNCARegionMask.top | HitTestNCARegionMask.left):
                return _resizable ? HTTOPLEFT : drag;
            case (byte)(HitTestNCARegionMask.top | HitTestNCARegionMask.right):
                return _resizable ? HTTOPRIGHT : drag;
            case (byte)(HitTestNCARegionMask.bottom | HitTestNCARegionMask.left):
                return _resizable ? HTBOTTOMLEFT : drag;
            case (byte)(HitTestNCARegionMask.bottom | HitTestNCARegionMask.right):
                return _resizable ? HTBOTTOMRIGHT : drag;
            case (byte)HitTestNCARegionMask.client:
                return drag;
            default:
                return HTNOWHERE;
        }



    }
}
